package com.jxin.mall.config.configuration;

import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.jxin.mall.config.consts.RedisTempTypeEnum;
import com.jxin.mall.config.plan.RedisSerializerPlan;
import com.jxin.mall.config.properties.CacheConfigProperties;
import com.jxin.mall.core.proxy.CatchThrowsProxy;
import com.jxin.mall.core.util.EnvironmentUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.List;
import java.util.Map;

/**
 * 缓存配置类
 * @author 蔡佳新
 * @version 1.0
 * @since jdk 1.8
 */
@EnableCaching
@Configuration
@Slf4j
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties(CacheProperties.class)
@ConditionalOnClass({CacheProperties.Redis.class, RedisCacheConfiguration.class})
public class CacheConfiguration implements EnvironmentAware {
    private ConfigurableEnvironment environment;
    /**redis的缓存配置*/
    private final CacheProperties cacheProperties;

    /**
     * 初始化redis的缓存配置
     * @param cacheProperties redis的缓存配置
     */
    public CacheConfiguration(CacheProperties cacheProperties) {
        this.cacheProperties = cacheProperties;
    }
    /**
     * 自定义springboot对redis缓存的支持
     * @param  redisConnectionFactory redis连接工厂
     * @return {@link RedisCacheManager} springboot的redis缓存管理对象
     * @author 蔡佳新
     */
    @Bean("redisCacheManager")
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory){
        //采用带锁的redis缓存写入实例
        RedisCacheWriter redisCacheWriter = (RedisCacheWriter) new CatchThrowsProxy().bind(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory));
        //initialCacheNames未调用，.cacheDefaults(getRedisCacheConfiguration())也没用撒
        RedisCacheManager.RedisCacheManagerBuilder redisCacheManagerBuilder = RedisCacheManager.RedisCacheManagerBuilder
                .fromCacheWriter(redisCacheWriter);

        //拿到所有缓存名
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            Map<String, RedisCacheConfiguration> map = Maps.newHashMap();
            cacheNames.forEach(name -> {
                String[] cacheParams = StrUtil.split(name.trim(), StrUtil.COMMA);
                RedisCacheConfiguration redisCacheConfiguration = getRedisCacheConfiguration();
                if (cacheParams.length == 2){
                    try{
                        redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofSeconds(Long.valueOf(cacheParams[1])));
                    }catch (Exception e){
                        log.error("redis超时时间格式错误,将采用默认配置.错误信息:{}", e.getMessage(), e);
                    }

                }
                map.put(cacheParams[0], redisCacheConfiguration);
            });
            redisCacheManagerBuilder.withInitialCacheConfigurations(map);
        }
        return redisCacheManagerBuilder.build();

    }


    /**
     * 获取redis缓存配置实例
     * @return {@link RedisCacheConfiguration} redis配置实例
     * @author 蔡佳新
     */
    private RedisCacheConfiguration getRedisCacheConfiguration(){
        RedisCacheConfiguration result = RedisCacheConfiguration.defaultCacheConfig();
        // 安排序列化策略
        result = result.serializeKeysWith(RedisSerializationContext.SerializationPair
                            .fromSerializer(new StringRedisSerializer()))
                       .serializeValuesWith(RedisSerializationContext.SerializationPair
                            .fromSerializer(RedisSerializerPlan.valueSerializer(RedisTempTypeEnum.CACHE_TEMP.getType())));
        // 拿环境变量改变默认配置
        final CacheConfigProperties cacheConfigProperties = EnvironmentUtil.resolveSetting(CacheConfigProperties.class, "redis-cache", getEnvironment());
        //因为是必填项，所以不用判空
        result = result.entryTtl(cacheConfigProperties.getExpiration());

        if (!cacheConfigProperties.isAllowNullValues()){
            result = result.disableCachingNullValues();
        }
        if (!cacheConfigProperties.isUseKeyPrefix()){
            result = result.disableKeyPrefix();
        }else{
            result = result.prefixKeysWith(cacheConfigProperties.getPrefixKeys());
        }
        return result;

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment) environment;
    }

    public ConfigurableEnvironment getEnvironment() {
        return environment;
    }
}
